#!/usr/bin/env python
# -*- coding: utf-8 -*-
#=======================================#
# @author: RedSoxFan and NerfSoldier
# @copyright: 2012 RedSoxFan and NerfSoldier
# @license: GNU General Public License v3.0 
#           <http://www.gnu.org/copyleft/gpl.html>
# @note: Java is a trademark of Oracle
# @summary: Java Parser
#=======================================#

#=======================================#
# Imports
#=======================================#
# Python
import fnmatch
import os
from PyQt4.QtCore import QChar
from PyQt4.QtGui import QTextCharFormat, QTextCursor, QTextBlockUserData
import re
import socket
import subprocess
import sys
import time
import urllib2
from zipfile import ZipFile
# DarkDev
from editor import Editor
from parsers import Parser, CodeCompletionPopup
#=======================================#
class Java(Parser):
    class State(object):
        START = 0
        NUMBER = 1
        STRING = 2
        CHARACTER = 3
        VARIABLE = 4
        MULTIPLE_LINE_COMMENT = 5
        JAVADOC = 6
        INVARDECL = 7
        INGENERIC = 8
        @staticmethod
        def style(state):
            try:
                return (lambda k:k and k[0] or None)(
                        [Java.style[k.title().replace("_","")] 
                         for k,v in Java.State.__dict__.iteritems() 
                         if v==state]
                       )
            except:
                return QTextCharFormat()
        
    description = "Java"
    filefilter = "*.java"
    key = "Java"
    javagate = None # Created On Module Initialization
    introspec = None # Created On Module Initialization
    primitive = ["boolean","byte","char","double","float","int","long","short"]
    primrettypes = primitive+["void"]
    keywords = ["continue","for","new","switch","assert","default","package","do","if",
                "this","break","implements","throw", "else","import","throws","case",
                "enum","instanceof","return", "catch","extends","try","interface",
                "class","finally","super","while"]
    visibility = ["public","protected","private"]
    notimplemented = ["const","goto"]
    # {"Modifier":[Class,Interface,Enum,Method,Variable]}
    modifiers = {"abstract":[1,0,0,1,0],"final":[1,0,1,0,1],"static":[1,1,1,1,1],
                 "strictfp":[1,1,1,1,0],"native":[0,0,0,1,0],"transient":[0,0,0,0,1],
                 "volatile":[0,0,0,0,1],"synchronized":[0,0,0,1,0]}
    constants = ["true","false","null"]
    reserved = primrettypes+keywords+modifiers.keys()+constants+notimplemented+visibility
    javalang = None # Created On Module Initialization
    symbols = ["*","/","%","+","-","!","~","?",":","|","^","&","=","<",">","(",")","[","]",".","{","}",";",","]
    
    #===========================================================================
    # Static and Class Methods
    #===========================================================================
    @staticmethod
    def init(darkdev):
        try:
            for k in ["JDK"]:
                darkdev.splash.setSecondLine("Initializing Java - Searching For %s"%k)
                if "%s.General.%s"%(Java.key,k) not in darkdev.ddcg:
                    j = Java.search(k,darkdev)
                    if j:
                        darkdev.ddcg["%s.General.%s"%(Java.key,k)] = j 
                        darkdev.logger.info("%s Found - %r"%(k,j))
                    else: darkdev.logger.warning("%s Not Found"%k)
            if "%s.General.JDK"%Java.key in darkdev.ddcg:
                darkdev.splash.setSecondLine("Initializing Java - Starting JavaGate")
                Java.javagate = JavaGate(darkdev.ddcg["%s.General.JDK"%Java.key],darkdev)
                darkdev.logger.debug("JavaGate Initialized Successfully")
                darkdev.splash.setSecondLine("Initializing Java - Retrieving Java Info")
                for v,k in zip(Java.javagate.jreinfo(),["JREVENDOR","JREVENDORURL","JAVAVERSION"]):
                    darkdev.ddcg["%s.General.%s"%(Java.key,k)] = v
                darkdev.splash.setSecondLine("Initializing Java - Searching JDK Path") 
                Java.introspec = JavaIntrospec(Java.javagate,darkdev.ddcg["%s.General.JDK"%Java.key],darkdev)
                Java.javalang = Java.introspec["java.lang"]
            darkdev.logger.debug("%s Initialized Successfully"%Java.description)
            return True
        except Exception, err:
            darkdev.logger.warning("Java.init - %r"%err)
            if Java.javagate: Java.javagate.deinit()
            return False
    @classmethod
    def deinit(cls,darkdev):
        try:
            Java.javagate.deinit()
            darkdev.logger.debug("%s Deinitialized Successfully"%cls.description)
            return True
        except Exception, err:
            darkdev.logger.warning("Java.deinit - %r"%err)
            return False
    @staticmethod
    def search(key,darkdev):
        if key=="JDK":
            if sys.platform.startswith("linux"):
                if os.path.exists("/usr/bin/javac"):
                    return os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath("/usr/bin/javac"))))
            elif sys.platform.startswith("win"):
                for path in ["C:/Program Files (x86)/Java/","C:/Program Files/Java/","C:/Java","/Program Files (x86)/Java/","/Program Files/Java/","/Java","jdk"]:
                    if os.path.exists(path):
                        for f in os.listdir(path):
                            if "jdk" in f:
                                return os.path.abspath(os.path.join(path,f))
    @staticmethod
    def locate(pattern, root):
        for path, _, files in os.walk(os.path.abspath(root)):
            for filename in fnmatch.filter(files, pattern):
                yield os.path.join(path, filename)
                
    #===========================================================================
    # Instance Methods
    #===========================================================================
    def __contains__(self,k):
        return bool(self[k])
    
    def __getitem__(self,k,introspec=True):
        '''
        Get The Item (Package, Class, Interface, or Enum)
        For Packages, only the contents of the requested layer is guaranteed
        For all others, there is full hierarchical support at the requested moment 
        '''
        p=0
        gen = [""]
        if k!="<default>":
            while k:
                if k[-1]=="<": p+=1
                elif k[-1]==">": p-=1
                if p==0:
                    if k[-1]=="<": k=k[:-1]
                    break
                elif k[-1]=="," and p==-1: gen+=[""]
                elif k[-1]!=">" or p!=-1: gen[-1]=k[-1]+gen[-1]
                k=k[:-1]
        
        if k.startswith("%s.%s"%(self.package_name(),os.path.basename(self.fname)[:-5])):
            p = self.__get_outline()
            if k=="%s.%s"%(self.package_name(),os.path.basename(self.fname)[:-5]): return Java.introspec.genericize(p, gen)
            slots = k.replace("$",".").replace("%s.%s."%(self.package_name(),os.path.basename(self.fname)[:-5]),"").split(".")
        else:
            p = self.project.java
            slots = k.replace("$",".").split(".")
        for s in slots:
            if p==self.project.java:
                g = s in p and [p[s]]
            elif p[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_PACKGAGE:
                for i in [JavaIntrospec.INDEX_PKG_SUBPACKAGES,JavaIntrospec.INDEX_PKG_CLASSES,JavaIntrospec.INDEX_PKG_INTERFACES,JavaIntrospec.INDEX_PKG_ENUMS]:
                    g = [x for x in p[i] if x[JavaIntrospec.INDEX_SIMPLENAME]==s]
                    if g: break
            elif p[JavaIntrospec.INDEX_KEY] in [JavaIntrospec.KEY_CLASS,JavaIntrospec.KEY_INTERFACE,JavaIntrospec.KEY_ENUM]:
                for i in [JavaIntrospec.INDEX_CLS_IFC_ENM_CLASSES,JavaIntrospec.INDEX_CLS_IFC_ENM_INTERFACES,JavaIntrospec.INDEX_CLS_IFC_ENM_ENUMS]:
                    g = [x for x in p[i] if x[JavaIntrospec.INDEX_SIMPLENAME]==s]
                    if g: break
            if g: p=g[0]
            else:p=None; break
        if not p:
            if introspec: return Java.introspec[k]
            return None
        if p[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_PACKGAGE and k in Java.introspec and introspec:
            ji = Java.introspec[k]
            if ji[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_PACKGAGE:
                return (JavaIntrospec.KEY_PACKGAGE,p[JavaIntrospec.INDEX_NAME],p[JavaIntrospec.INDEX_SIMPLENAME],
                        p[JavaIntrospec.INDEX_PKG_CLASSES]+ji[JavaIntrospec.INDEX_PKG_CLASSES],
                        p[JavaIntrospec.INDEX_PKG_INTERFACES]+ji[JavaIntrospec.INDEX_PKG_INTERFACES],
                        p[JavaIntrospec.INDEX_PKG_ENUMS]+ji[JavaIntrospec.INDEX_PKG_ENUMS],
                        p[JavaIntrospec.INDEX_PKG_SUBPACKAGES]+list(x for x in ji[JavaIntrospec.INDEX_PKG_SUBPACKAGES] if x[JavaIntrospec.INDEX_NAME] not in tuple(y[JavaIntrospec.INDEX_NAME] for y in p[JavaIntrospec.INDEX_PKG_SUBPACKAGES])))
        return Java.introspec.genericize(p, gen)
    
    def __setitem__(self,k,v):
        '''
        Setting Items Is Only Allowed For The Project DB. 
        If k is in the Standard JRE, this will have no effect
        '''
        if k in Java.introspec: return # No Setting Arrays / Built-In
        slots = k.replace("$",".").split(".")
        prt = self.__getitem__(".".join(slots[:-1]),introspec=False)
        if not prt: prt=Java.introspec.genpkg(".".join(slots[:-1]),self.project.java)
        if v[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_CLASS:
            for x in prt[JavaIntrospec.INDEX_PKG_CLASSES]:
                if x[JavaIntrospec.INDEX_NAME] == k:
                    prt[JavaIntrospec.INDEX_PKG_CLASSES].remove(x)
                    break
            prt[JavaIntrospec.INDEX_PKG_CLASSES] += [v]
        elif v[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_INTERFACE:
            for x in prt[JavaIntrospec.INDEX_PKG_INTERFACES]:
                if x[JavaIntrospec.INDEX_NAME] == k:
                    prt[JavaIntrospec.INDEX_PKG_INTERFACES].remove(x)
                    break
            prt[JavaIntrospec.INDEX_PKG_INTERFACES] += [v]
        elif v[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_ENUM:
            for x in prt[JavaIntrospec.INDEX_PKG_ENUMS]:
                if x[JavaIntrospec.INDEX_NAME] == k:
                    prt[JavaIntrospec.INDEX_PKG_ENUMS].remove(x)
                    break
            prt[JavaIntrospec.INDEX_PKG_ENUMS] += [v]
            
    def allClassTypesInPackage(self,pkg):
        '''
        List all class, interface, and enum names in the package
        '''
        return self.classesInPackage(pkg)+self.interfacesInPackage(pkg)+self.enumsInPackage(pkg)
            
    def create(self):
        '''
        Override of Parser.create
        '''
        try:
            self.project.java
        except AttributeError:
            self.project.java = {}
            # Initial Scan
            for path in Java.locate("*.java", str(self.project.toolTip(0))):
                j = Editor(self.project,Java,path)
                del j
        self.once = False
        self.rehighlight()
    
    def classesInPackage(self,pkg):
        '''
        Return a list of class names in package
        '''
        if pkg not in self: return []
        return [x[JavaIntrospec.INDEX_SIMPLENAME] for x in self[pkg][JavaIntrospec.INDEX_PKG_CLASSES]]
    
    def enumsInPackage(self,pkg):
        '''
        Return a list of enums names in package
        '''
        if pkg not in self: return []
        return [x[JavaIntrospec.INDEX_SIMPLENAME] for x in self[pkg][JavaIntrospec.INDEX_PKG_ENUMS]]
    
    def highlightBlock(self,text):
        tokens = []
        vardecl = []
        gendecl = []
        state = max(0,self.previousBlockState())
        invardecl = self.currentBlock().blockNumber()>0 and self.currentBlock().previous().userData().inVarDeclType[:] or (False,0)
        paren = invardecl[1]
        ingeneric = self.currentBlock().blockNumber()>0 and self.currentBlock().previous().userData().ingeneric[:] or []
        brack = self.currentBlock().blockNumber()>0 and self.currentBlock().previous().userData().brack or 0
        if state in [Java.State.INVARDECL,Java.State.INGENERIC]: state=Java.State.START
        scope = self.currentBlock().blockNumber()>0 and self.currentBlock().previous().userData().endscope[:] or ["File"]
        start = 0
        index = 0
        text+=" "
        while index < text.length():
            ch = index<text.length() and text.at(index) or QChar()
            cha = ch.toAscii()
            nx = index+1<text.length() and text.at(index+1) or QChar()
            nxa = nx.toAscii()
            at = index+2<text.length() and text.at(index+2) or QChar()
            ata = at.toAscii()
            if state==Java.State.START:
                start=index
                if cha=="/" and nxa=="/":
                    self.setFormat(index, text.length()-index, Java.style["SingleLineComment"])
                    tokens += [("SingleLineComment",str(text)[index:],scope)]
                    index=text.length()
                elif cha=="@":
                    self.setFormat(index, text.length()-index, Java.style["Annotation"])
                    tokens += [("Annotation",str(text)[index:],scope)]
                    index=text.length()
                elif ch.isDigit() or (cha=="." and nx.isDigit()):
                    state=Java.State.NUMBER
                elif ch.isLetter() or cha=='_':
                    state=Java.State.VARIABLE
                elif cha=='"':
                    state=Java.State.STRING
                elif cha=="'":
                    state=Java.State.CHARACTER
                elif cha=="/" and nxa=="*":
                    try:
                        if text[index+2]=="*": 
                            state=Java.State.JAVADOC
                            index+=1
                        else:
                            state=Java.State.MULTIPLE_LINE_COMMENT
                    except:
                        state=Java.State.MULTIPLE_LINE_COMMENT
                elif cha in Java.symbols:
                    self.setFormat(index, 1, Java.style["Symbol"])
                    if len(ingeneric):
                        ingeneric[-1][1] += str(text[index])
                    else:
                        tokens += [("Symbol",str(text)[index],scope)]
                    if cha=="}": scope = scope[:-1]
                    elif cha=="{": self.__addScope(scope,tokens,self.currentBlock())
                    elif cha==";": invardecl = (False,paren)
                    elif cha=="(": paren+=1
                    elif cha==")": paren-=1
                    elif len(ingeneric):
                        if cha==">":
                            if brack-1==0: tokens += [(ingeneric[-1][0],ingeneric[-1][1],ingeneric[-1][2])]
                            brack-=1
                            ingeneric=ingeneric[:-1]
                elif cha==" ":
                    if len(ingeneric):
                        ingeneric[-1][1] += str(text[index])
                    else:
                        tokens += [("Whitespace"," ",scope)]
                index+=1
            elif state==Java.State.NUMBER:
                if ch.isDigit() or str(cha).lower() in ["a","b","c","d","e","f","l","x","."]:
                    index+=1
                else:
                    self.setFormat(start, index-start, Java.style["Number"])
                    tokens += [("Number",str(text[start:index]),scope)]
                    state=Java.State.START
            elif state==Java.State.CHARACTER:
                if cha=="'":
                    index+=1
                    self.setFormat(start, index-start, Java.style["Character"])
                    tokens += [("Character",str(text[start:index]),scope)]
                    state=Java.State.START
                elif cha=="\\":
                    index+=2
                else:
                    index+=1
            elif state==Java.State.STRING:
                if cha=='"':
                    index+=1
                    tokens += [("String",str(text[start:index]),scope)]
                    self.setFormat(start, index-start, Java.style["String"])
                    state=Java.State.START
                elif cha=="\\":
                    index+=2
                else:
                    index+=1
            elif state==Java.State.VARIABLE:
                if ch.isDigit() or ch.isLetter() or cha=='_':
                    index+=1
                else:
                    # Primitive Types
                    if text[start:index] in Java.primitive:
                        self.setFormat(start, index-start, Java.style["Keyword"])
                        x=0
                        while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                            x=1
                            self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                            index+=cha=="[" and 2 or 3
                            ch = index<text.length() and text.at(index) or QChar()
                            cha = ch.toAscii()
                            nx = index+1<text.length() and text.at(index+1) or QChar()
                            nxa = nx.toAscii()
                            at = index+2<text.length() and text.at(index+2) or QChar()
                            ata = at.toAscii()
                        if x:
                            tokens += [("Class",str(text[start:index]),scope)]
                        else:
                            tokens += [("Keyword",str(text[start:index]),scope)]
                        state=Java.State.START
                    # Keyword
                    elif text[start:index] in Java.reserved:
                        self.setFormat(start, index-start, Java.style["Keyword"])
                        if len(ingeneric):
                            ingeneric[-1][1] += str(text[start:index])
                        else:
                            tokens += [("Keyword",str(text[start:index]),scope)]
                        state=Java.State.START
                    # Constructor
                    elif not(len(ingeneric)) and len(tokens)>=2 and tokens[-2][1]=="new" and tokens[-1][1]==" " and \
                            str(text[start:index]) in self.classes+self.interfaces+self.enums:
                        self.setFormat(start, index-start, Java.style["Constructor"])
                        if cha=="<" or nxa=="<":
                            brack+=1
                            ingeneric += [["Constructor",str(text[start:index]),scope]]
                        else:
                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                index+=cha=="[" and 2 or 3
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            if len(ingeneric):
                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                            else:
                                tokens += [("Constructor",str(text[start:index]),scope)]
                        state=Java.State.START
                    # ConstructorDeclaration
                    elif not(len(ingeneric)) and ((len(tokens)>=2 and tokens[-2][1] in ["public","protected","private"] and (cha=="(" or nxa=="(")) or \
                        str(text[start:index]) in self.classes+self.interfaces+self.enums and (cha=="(" or nxa=="(") and "(" not in scope[-1]):
                        self.setFormat(start, index-start, Java.style["ConstructorDeclaration"])
                        tokens += [("ConstructorDeclaration",str(text[start:index]),scope)]
                        state=Java.State.START
                    # ClassDeclaration
                    elif not(len(ingeneric)) and (len(tokens)>=2 and tokens[-2][1]=="class" and tokens[-1][1]==" "):
                        self.setFormat(start, index-start, Java.style["ClassDeclaration"])
                        if str(text[start:index]) not in self.classes:
                            self.classes += [str(text[start:index])]
                        tokens += [("Declaration",".".join(scope[1:]),scope)]
                        if cha=="<" or nxa=="<":
                            brack+=1
                            ingeneric += [["ClassDeclaration",str(text[start:index]),scope]]
                        else:
                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                index+=cha=="[" and 2 or 3
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            tokens += [("ClassDeclaration",str(text[start:index]),scope)]
                        state=Java.State.START
                    # Class
                    elif str(text[start:index]) in [x[JavaIntrospec.INDEX_SIMPLENAME] for x in Java.javalang[JavaIntrospec.INDEX_PKG_CLASSES]] or str(text[start:index]) in self.classes:
                        self.setFormat(start, index-start, Java.style["Class"])
                        if cha=="<" or nxa=="<":
                            brack+=1
                            ingeneric += [["Class",str(text[start:index]),scope]]
                        else:
                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                index+=cha=="[" and 2 or 3
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            if len(ingeneric):
                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                            else:
                                tokens += [("Class",str(text[start:index]),scope)]
                        state=Java.State.START
                    # InterfaceDeclaration
                    elif not(len(ingeneric)) and len(tokens)>=2 and tokens[-2][1]=="interface":
                        self.setFormat(start, index-start, Java.style["InterfaceDeclaration"])
                        if str(text[start:index]) not in self.interfaces:
                            self.interfaces += [str(text[start:index])]
                        tokens += [("Declaration",".".join(scope[1:]),scope)]
                        if cha=="<" or nxa=="<":
                            brack+=1
                            ingeneric += [["InterfaceDeclaration",str(text[start:index]),scope]]
                        else:
                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                index+=cha=="[" and 2 or 3
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            if len(ingeneric):
                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                            else:
                                tokens += [("InterfaceDeclaration",str(text[start:index]),scope)]
                    # Interface
                    elif (len(tokens)>=2 and tokens[-2][1]=="implements") or str(text[start:index]) in self.interfaces \
                            or str(text[start:index]) in [x[JavaIntrospec.INDEX_SIMPLENAME] for x in Java.javalang[JavaIntrospec.INDEX_PKG_INTERFACES]]:
                        self.setFormat(start, index-start, Java.style["Interface"])
                        if cha=="<" or nxa=="<":
                            brack+=1
                            ingeneric += [["Interface",str(text[start:index]),scope]]
                        else:
                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                index+=cha=="[" and 2 or 3
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            if len(ingeneric):
                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                            else:
                                tokens += [("Interface",str(text[start:index]),scope)]
                        state=Java.State.START
                    # extends Interface
                    elif len(tokens)>=2 and ((tokens[-2][1]=="extends" and tokens[-2][1]=="Interface")
                                                 or (tokens[-2][1]=="," and tokens[-2][1]=="Interface")):
                        self.setFormat(start, index-start, Java.style["Interface"])
                        tokens += [("Interface",str(text[start:index]),scope)]
                        state=Java.State.START
                    # extends Class
                    elif len(tokens)>=4 and (tokens[-2][1]=="extends" and tokens[-4][1]=="Class"):
                        self.setFormat(start, index-start, Java.style["Class"])
                        tokens += [("Class",str(text[start:index]),scope)]
                        state=Java.State.START
                    # EnumDeclaration
                    elif not(len(ingeneric)) and (len(tokens)>=2 and tokens[-2][1]=="enum"):
                        self.setFormat(start, index-start, Java.style["EnumDeclaration"])
                        if str(text[start:index]) not in self.enums:
                            self.enums += [str(text[start:index])]
                        tokens += [("Declaration",".".join(scope[1:]),scope)]
                        if cha=="<" or nxa=="<":
                            brack+=1
                            ingeneric += [["EnumDeclaration",str(text[start:index]),scope]]
                        else:
                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                index+=cha=="[" and 2 or 3
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            tokens += [("EnumDeclaration",str(text[start:index]),scope)]
                        state=Java.State.START
                    # Enum
                    elif str(text[start:index]) in self.enums or str(text[start:index]) in [x[JavaIntrospec.INDEX_SIMPLENAME] for x in Java.javalang[JavaIntrospec.INDEX_PKG_ENUMS]]:
                        self.setFormat(start, index-start, Java.style["Enum"])
                        if cha=="<" or nxa=="<":
                            brack+=1
                            ingeneric += [["Enum",str(text[start:index]),scope]]
                        else:
                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                index+=cha=="[" and 2 or 3
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            if len(ingeneric):
                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                            else:
                                tokens += [("Enum",str(text[start:index]),scope)]
                        state=Java.State.START
                    elif cha=="(" or (cha==" " and nxa=="("):
                        # MethodDeclaration
                        if len(tokens)>=2 and tokens[-1][1]==" " and (tokens[-2][0] in ["Generic","Class","Interface","Enum"] or tokens[-2][1] in Java.primrettypes):
                            self.setFormat(start, index-start, Java.style["MethodDeclaration"])
                            tokens += [("MethodDeclaration",str(text[start:index]),scope)]
                            state=Java.State.START
                        # Enum Constructor Call
                        elif scope and scope[-1] in self.enums:
                            self.setFormat(start, index-start, Java.style["VariableDeclaration"])
                            tokens += [("VariableDeclaration",str(text[start:index]),scope)]
                            invardecl = (scope[-1],paren)
                            vardecl += [(invardecl[0],str(text[start:index]),scope)]
                        # Method
                        else:
                            self.setFormat(start, index-start, Java.style["Method"])
                            tokens += [("Method",str(text[start:index]),scope)]
                            state=Java.State.START
                    # Package
                    elif (len(tokens)>=2 and tokens[-2][1]=="package"):
                        if cha == ".": index+=1
                        else:
                            self.setFormat(start, index-start, Java.style["Package"])
                            tokens += [("Package",str(text[start:index]),scope)]
                            state=Java.State.START
                    # Import
                    elif (len(tokens)>=2 and tokens[-2][1]=="import"):
                        if cha in [".","*"]: index+=1
                        else:
                            tokens += [("Import",str(text[start:index]),scope)]
                            t = text[start:index]
                            ind = 0
                            prt = ""
                            unknown = False
                            for pt in str(t).split("."):
                                prt+=pt
                                if pt=="*":
                                    if not unknown:
                                        self.setFormat(start+ind,  len(pt), Java.style["Package"])
                                        try:
                                            self.classes += self.classesInPackage(str(t[:-2]))
                                            self.interfaces += self.interfacesInPackage(str(t[:-2]))
                                            self.enums += self.enumsInPackage(str(t[:-2]))
                                        except:
                                            pass
                                    continue
                                c = self.jType(str(prt))
                                if c==JavaIntrospec.KEY_INTERFACE:
                                    self.setFormat(start+ind, len(pt), Java.style["Interface"])
                                    if prt==str(t):
                                        self.interfaces += [str(text[start+ind:start+ind+len(pt)])]
                                        tokens += [("Interface",str(text[start+ind:start+ind+len(pt)]),scope)]                                              
                                elif c==JavaIntrospec.KEY_ENUM:
                                    self.setFormat(start+ind, len(pt), Java.style["Enum"])
                                    if prt==str(t):
                                        self.enums += [str(text[start+ind:start+ind+len(pt)])]   
                                        tokens += [("Enum",str(text[start+ind:start+ind+len(pt)]),scope)]
                                elif c==JavaIntrospec.KEY_CLASS:
                                    self.setFormat(start+ind, len(pt), Java.style["Class"])
                                    if prt==str(t):
                                        self.classes += [str(text[start+ind:start+ind+len(pt)])]
                                        tokens += [("Class",str(text[start+ind:start+ind+len(pt)]),scope)]   
                                elif c==JavaIntrospec.KEY_PACKGAGE:
                                    self.setFormat(start+ind,  len(pt), Java.style["Package"])
                                else:
                                    unknown = True
                                if prt!=str(t):
                                    prt+="."
                                    ind+=len(pt)+1
                                    self.setFormat(start+ind-1, 1, Java.style["Symbol"])
                            state=Java.State.START
                    else:
                        # Fully Qualified Name
                        if str(text[start:index]) in Java.introspec.db.keys()+self.project.java.keys():
                            nw = not(len(ingeneric)) and len(tokens)>=2 and tokens[-2][1]=="new" and tokens[-1][1]==" "
                            while ch.isDigit() or ch.isLetter() or cha in ['.','_']:
                                index+=1
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                            t = text[start:index]
                            ind = 0
                            prt = ""
                            for pt in str(t).split("."):
                                prt+=pt
                                c = self.jType(str(prt))
                                if c==JavaIntrospec.KEY_INTERFACE:
                                    if prt==str(t):
                                        if cha=="<" or nxa=="<":
                                            brack+=1
                                            ingeneric += [["Interface",str(text[start:index]),scope]]
                                        else:
                                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                                index+=cha=="[" and 2 or 3
                                                ch = index<text.length() and text.at(index) or QChar()
                                                cha = ch.toAscii()
                                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                                nxa = nx.toAscii()
                                                at = index+2<text.length() and text.at(index+2) or QChar()
                                                ata = at.toAscii()
                                            if len(ingeneric):
                                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                                            else:
                                                tokens += [("Interface",str(text[start:index]),scope)]
                                        self.setFormat(start+ind, len(pt), nw and Java.style["Constructor"] or Java.style["Interface"])
                                    else:
                                        self.setFormat(start+ind, len(pt), Java.style["Interface"])                                              
                                elif c==JavaIntrospec.KEY_ENUM:
                                    if prt==str(t):
                                        if cha=="<" or nxa=="<":
                                            brack+=1
                                            ingeneric += [["Enum",str(text[start:index]),scope]]
                                        else:
                                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                                index+=cha=="[" and 2 or 3
                                                ch = index<text.length() and text.at(index) or QChar()
                                                cha = ch.toAscii()
                                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                                nxa = nx.toAscii()
                                                at = index+2<text.length() and text.at(index+2) or QChar()
                                                ata = at.toAscii()
                                            if len(ingeneric):
                                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                                            else:
                                                tokens += [("Enum",str(text[start:index]),scope)]
                                        self.setFormat(start+ind, len(pt), nw and Java.style["Constructor"] or Java.style["Enum"])
                                    else:
                                        self.setFormat(start+ind, len(pt), Java.style["Enum"])
                                elif c==JavaIntrospec.KEY_CLASS:
                                    if prt==str(t):
                                        if cha=="<" or nxa=="<":
                                            brack+=1
                                            ingeneric += [["Class",str(text[start:index]),scope]]
                                        else:
                                            while (cha=="[" and nxa=="]") or (cha=="." and nxa=="." and ata=="."):
                                                self.setFormat(index, cha=="[" and 2 or 3, Java.style["Symbol"])
                                                index+=cha=="[" and 2 or 3
                                                ch = index<text.length() and text.at(index) or QChar()
                                                cha = ch.toAscii()
                                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                                nxa = nx.toAscii()
                                                at = index+2<text.length() and text.at(index+2) or QChar()
                                                ata = at.toAscii()
                                            if len(ingeneric):
                                                ingeneric[-1][1] += self.__findMemberImport(str(text[start:index]))
                                            else:
                                                tokens += [("Class",str(text[start:index]),scope)]
                                        self.setFormat(start+ind, len(pt), nw and Java.style["Constructor"] or Java.style["Class"])
                                    else:
                                        self.setFormat(start+ind, len(pt), Java.style["Class"])   
                                elif c==JavaIntrospec.KEY_PACKGAGE:
                                    self.setFormat(start+ind,  len(pt), Java.style["Package"])
                                if prt!=str(t):
                                    prt+="."
                                    ind+=len(pt)+1
                                    self.setFormat(start+ind-1, 1, Java.style["Symbol"])
                        # VariableDeclaration
                        elif len(tokens)>=2 and (tokens[-2][0] in ["Class","Enum","Interface"] or tokens[-2][1] in Java.primitive) and tokens[-1][1]==" ": 
                            self.setFormat(start, index-start, Java.style["VariableDeclaration"])
                            tokens += [("VariableDeclaration",str(text[start:index]),scope)]
                            c = len(tokens)>=3 and tokens[-3][1] or None
                            x = tokens[-1][1]
                            s = ""
                            while cha=="[" and nxa=="]":
                                self.setFormat(index, 2, Java.style["Symbol"])
                                tokens += [("Symbol","[",scope)]
                                tokens += [("Symbol","]",scope)]
                                s+="[]"
                                index+=2
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            vardecl += [(c+s,x,scope)]
                            if c and c!="(":
                                invardecl = (c,paren)
                        # Secondary VariableDeclaration
                        elif invardecl[0] and paren==invardecl[1] and self.__isvardecl(tokens,self.currentBlock()):
                            self.setFormat(start, index-start, Java.style["VariableDeclaration"])
                            tokens += [("VariableDeclaration",str(text[start:index]),scope)]
                            x = tokens[-1][1]
                            s = ""
                            while cha=="[" and nxa=="]":
                                self.setFormat(index, 2, Java.style["Symbol"])
                                tokens += [("Symbol","[",scope)]
                                tokens += [("Symbol","]",scope)]
                                s+="[]"
                                index+=2
                                ch = index<text.length() and text.at(index) or QChar()
                                cha = ch.toAscii()
                                nx = index+1<text.length() and text.at(index+1) or QChar()
                                nxa = nx.toAscii()
                                at = index+2<text.length() and text.at(index+2) or QChar()
                                ata = at.toAscii()
                            vardecl += [(invardecl[0]+s,x,scope)]
                        # Class/Interface/Enum Generic Variables
                        elif ingeneric and ingeneric[-1][0] in ["ClassDeclaration","InterfaceDeclaration","EnumDeclaration"]:
                            self.setFormat(start, index-start, Java.style["GenericDeclaration"])
                            gendecl += [(str(text[start:index]),scope)]
                            tokens += [("GenericDeclaration",str(text[start:index]),scope)]
                        # Check To See If Generic
                        elif self.__isGeneric(str(text[start:index]),scope):
                            self.setFormat(start, index-start, Java.style["Generic"])
                            tokens += [("Generic",str(text[start:index]),scope)]
                        # Check To See If In Enum
                        elif scope and scope[-1] in self.enums:
                            self.setFormat(start, index-start, Java.style["VariableDeclaration"])
                            tokens += [("VariableDeclaration",str(text[start:index]),scope)]
                            invardecl = (scope[-1],paren)
                            vardecl += [(invardecl[0],str(text[start:index]),scope)]
                        # Variable
                        else:
                            self.setFormat(start, index-start, Java.style["Variable"])
                            if ingeneric: 
                                ingeneric[-1][1]+=str(text[start:index])
                            else:
                                tokens += [("Variable",str(text[start:index]),scope)]
                        state=Java.State.START
            elif state in [Java.State.MULTIPLE_LINE_COMMENT,Java.State.JAVADOC]:
                fmt = Java.style[state==Java.State.MULTIPLE_LINE_COMMENT and "MultipleLineComment" or "Javadoc"]
                if "*/" in text[index:]:
                    index = text.indexOf("*/")+2
                    self.setFormat(start, index-start, fmt)
                    state=Java.State.START
                    tokens += [(state==Java.State.MULTIPLE_LINE_COMMENT and "MultipleLineComment" or "Javadoc",str(text[start:index]),scope)]
                else:
                    self.setFormat(start, len(text)-start, fmt)
                    index = text.length()
                    tokens += [(state==Java.State.MULTIPLE_LINE_COMMENT and "MultipleLineComment" or "Javadoc",str(text[start:index]),scope)]
        # Set Block State
        if state in [Java.State.MULTIPLE_LINE_COMMENT,Java.State.JAVADOC]:
            self.setCurrentBlockState(state)
        elif invardecl:
            self.setCurrentBlockState(Java.State.INVARDECL)
        elif ingeneric:
            self.setCurrentBlockState(Java.State.INGENERIC)
        else:
            state = Java.State.START
            self.setCurrentBlockState(Java.State.START)
        # Set Block Tokens
        self.setCurrentBlockUserData(JavaBlockUserData(tokens,vardecl,scope,invardecl,brack,ingeneric,gendecl))
        # If Cursor In Block (And Not Inside MultipleLineComments/Javadocs), Run Code Completion Popup
        if state==Java.State.START and self.sv.textCursor().block()==self.currentBlock():
            self.runCodeCompletionPopup(text[:-1],tokens)
        # Test To See If We Update The DB (FIRST RUN)
        if not(self.once) and 0!=self.currentBlock().blockNumber()==self.document().lastBlock().blockNumber():
            self.update_db()
            self.once = True
    
    def insert(self,a,qc):
        '''
        Insert Text Selected In The CodeCompletionPopup
        '''
        self.frame.hide()
        for _ in xrange(len(qc.prefix)): self.sv.moveCursor(QTextCursor.Left,QTextCursor.KeepAnchor)
        self.sv.textCursor().removeSelectedText()
        self.sv.insertPlainText(re.sub(".*\<title\>(.*)\</title\>.*","\\1",str(a)))  
        
    def interfacesInPackage(self,pkg):
        '''
        Return a list of interface names in package
        '''
        if pkg not in self: return []
        return [x[JavaIntrospec.INDEX_SIMPLENAME] for x in self[pkg][JavaIntrospec.INDEX_PKG_INTERFACES]]  
    
    def javadoc(self,a,qc):
        '''
        Show the Javadoc DocumentationPopup for the item a from the CodeCompletionPopup qc
        @param a: Item Text
        @param qc: CodeCompletionPopup
        '''
        try:
            url = re.sub("\<[^\>]*\>","",re.sub('.*\<meta http-equiv="javadoc" content="(.*?)"/\>.*',"\\1",str(a)))
            if "/.html" in url:
                self.frame.hide() 
                return
            txt = urllib2.urlopen(url).read()
            if "#" not in url or url.endswith("#"):
                txt=txt.split("<HR>")[3]
            else:
                txt=txt[txt.find('<A NAME="%s">'%url[url.find("#")+1:]):]
                txt=txt[txt.find("</H3>")+5:txt.find("</DL>")]
            self.frame.setText(txt)
            self.frame.setCodeCompletionPopup(qc)
            cursor = self.sv.cursorRect()
            cursor.translate(self.sv.ln.width()+qc.width(),self.sv.toolbar.height())
            self.frame.move(cursor.bottomLeft())
            self.frame.show()
            self.frame.setFixedWidth(self.frame.document().size().width()+self.frame.verticalScrollBar().sizeHint().width()+5)
        except:
            pass    
        
    def jType(self,cls):
        '''
        Return the KEY for cls. cls must be either a Package, Class, Interface, or Enum. 
        Fields, Methods, and Constructors are not currently supported by __getitem__
        '''
        c = self[cls]
        return c and c[JavaIntrospec.INDEX_KEY] or ""
        
    def package_name(self):
        '''
        Name Of Package (Based On Folder Structure)
        '''
        folder = os.path.dirname(self.fname)
        return folder[folder.rfind('src')+4:].replace("\\",".").replace("/",".") or "<default>"
    
    def rehighlight(self):
        '''
        Rehighlight Whole Document
        '''
        pkg = self.package_name()
        self.classes = self.classesInPackage(pkg)
        self.interfaces = self.interfacesInPackage(pkg)
        self.enums = self.enumsInPackage(pkg)
        Parser.rehighlight(self)
    
    def runCodeCompletionPopup(self,text,tok):
        '''
        Determine Code Completion Mode and Show Appropriate List
        '''
        # Hide Previous
        if self.qc: self.qc.hide()
        # Get Text To Cursor For Block
        text = text[:self.sv.textCursor().positionInBlock()]
        # Get Tokens To Cursor For Block
        txt = text
        tokens = []
        ign=False
        for t in tok:
            if ign and t[0] in ["Class","Interface","Enum"]: continue
            ign = False
            if not txt: break
            while txt and not str(txt).startswith(t[1]): txt=txt[1:]
            if txt:
                txt=txt[len(t[1]):]
                tokens+=[t]
            if t[0]=="Import": ign=True
        # Try To Populate CodeCompletionPopup
        try:
            if len([t for t in tokens if t[1]!=" "])==0:
                # Start Of Block
                pass
            elif tokens[-1][1]==" ":
                # After Space
                if tokens[-2][1]=="import": # All Root Packages
                    self.__showCodeCompletionPopup(self.codecompleter_rootpackages(self.sv))
                elif tokens[-2][1]=="new":
                    # TODO: new (w/o prefix)
                    pass
            elif tokens[-1][1]==".":
                # Right After .
                if tokens[-2][0] in ["Class","Interface","Enum"]:
                    pmem = self.__findMemberImport(tokens[-2][1])
                    self.__showCodeCompletionPopup(self.codecompleter_class(pmem,True,self.sv))
                else:
                    pmem =self.__findTokenClass(tokens[-2], len(tokens)>2 and tokens[:-2] or [])
                    if pmem in Java.primitive: self.__showCodeCompletionPopup(self.codecompleter_primitive(self.sv))
                    else: self.__showCodeCompletionPopup(self.codecompleter_class(pmem,False,self.sv))
            elif tokens[-1][0]=="Import":
                # import {IMPORT}
                mem=tokens[-1][1]
                if "." in mem:
                    mem,prt = mem[:mem.rfind(".")],mem[mem.rfind(".")+1:]
                    if self[mem][JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_PACKGAGE:
                        self.__showCodeCompletionPopup(self.codecompleter_packages(mem,self.sv,prefix=prt))
                    elif self[mem][JavaIntrospec.INDEX_KEY] in [JavaIntrospec.KEY_CLASS,JavaIntrospec.KEY_INTERFACE,JavaIntrospec.KEY_ENUM]:
                        self.__showCodeCompletionPopup(self.codecompleter_classcontents(mem,self.sv,prefix=prt))
                else:
                    self.__showCodeCompletionPopup(self.codecompleter_rootpackages(self.sv,prefix=mem))
            elif tokens[-3][1]=="new" and tokens[-2][1]==" ":
                # new {SOMETHING}
                pmem = self.__findMemberImport(tokens[-1][1])
                if pmem: self.__showCodeCompletionPopup(self.codecompleter_constructor(self[pmem][JavaIntrospec.INDEX_KEY],pmem,self.sv))
            elif tokens[-3][1]!=" " and tokens[-2][1]==".":
                # {SOMETHING}.{SOMETHING}
                if tokens[-3][0] in ["Class","Enum","Interface"]:
                    # Show Static Members Of Class With Prefix
                    pmem = self.__findMemberImport(tokens[-3][1])
                    if pmem: self.__showCodeCompletionPopup(self.codecompleter_class(pmem,True,self.sv,prefix=tokens[-1][1]))
                elif tokens[-3][0]=="String":
                    self.__showCodeCompletionPopup(self.codecompleter_class("java.lang.String",False,self.sv,prefix=tokens[-1][1]))
                elif tokens[-3][1]=="class":
                    self.__showCodeCompletionPopup(self.codecompleter_class("java.lang.Class",False,self.sv,prefix=tokens[-1][1]))
                elif tokens[-1][1]!=" ":
                    pmem =self.__findTokenClass(tokens[-3], len(tokens)>3 and tokens[:-3] or [])
                    if pmem in Java.primitive: self.__showCodeCompletionPopup(self.codecompleter_primitive(self.sv,prefix=tokens[-1][1]))
                    else: self.__showCodeCompletionPopup(self.codecompleter_class(pmem,False,self.sv,prefix=tokens[-1][1]))
        except:
            pass
        
    def save(self,fname=None):
        '''
        Save Contents To File fname. 
        If fname is any False value (as it is by default), the original filename will be used
        @keyword fname: Filename to save to (default: None)
        '''
        Parser.save(self,fname)
        if not fname:
            # Saving To Original File (NOT A COPY) So Update Project DB
            self.update_db()
            
    def update_db(self):
        '''
        Update Project Database
        '''
        out = self.__get_outline()
        if out and isinstance(out,tuple): self[out[JavaIntrospec.INDEX_NAME]] = out
                    
    #===========================================================================
    # Code Completion Functions
    #===========================================================================
    def codecompleter_class(self,cls,static,te,vis='public',prefix=""):
        '''
        List All Class/Interface/Enum Contents
        '''
        cls = self[cls]
        l = []
        for x in set(tuple(x for x in cls[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]+cls[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS])):
            if prefix and not x[JavaIntrospec.INDEX_NAME].lower().startswith(prefix.lower()): continue
            if x[JavaIntrospec.INDEX_CON_MTH_FLD_VISIBILITY]==vis and (not(static) or "static" in x[JavaIntrospec.INDEX_CON_MTH_FLD_MODIFIERS]):
                s = "static" in x[JavaIntrospec.INDEX_CON_MTH_FLD_MODIFIERS]
                if x[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_METHOD:
                    l += [Java.introspec.skeleton.format(NAME=x[JavaIntrospec.INDEX_NAME], 
                                               TAG=s and "strong" or "b",
                                               OTHER="(%s)"%(", ".join(self.__fix(x[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS][1]))),
                                               JTYPE=self.__fix(x[JavaIntrospec.INDEX_MTH_RETURNTYPE]),
                                               PKGMEM=x[JavaIntrospec.INDEX_CON_MTH_FLD_PARENT].replace(".","/").replace("$","."),
                                               MEMBER="%s(%s)"%(x[JavaIntrospec.INDEX_NAME],", ".join(x[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS][0])),
                                               KEY=x[JavaIntrospec.INDEX_KEY])]
                elif x[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_FIELD:
                    l += [Java.introspec.skeleton.format(NAME=x[JavaIntrospec.INDEX_NAME],
                                               TAG=s and "strong" or "b",
                                               OTHER="",
                                               JTYPE=self.__fix(x[JavaIntrospec.INDEX_FLD_TYPE]),
                                               PKGMEM=x[JavaIntrospec.INDEX_CON_MTH_FLD_PARENT].replace(".","/").replace("$","."),
                                               MEMBER="%s"%(x[JavaIntrospec.INDEX_NAME]),
                                               KEY=x[JavaIntrospec.INDEX_KEY])]
        for x in cls[JavaIntrospec.INDEX_CLS_IFC_ENM_CLASSES]+cls[JavaIntrospec.INDEX_CLS_IFC_ENM_INTERFACES]+cls[JavaIntrospec.INDEX_CLS_IFC_ENM_ENUMS]:
            if prefix and not x[JavaIntrospec.INDEX_SIMPLENAME].lower().startswith(prefix.lower()): continue
            if x[JavaIntrospec.INDEX_CLS_IFC_ENM_VISIBILITY]==vis and (not(static) or "static" in x[JavaIntrospec.INDEX_CLS_IFC_ENM_MODIFIERS]):
                l += [Java.introspec.skeleton.format(NAME=x[JavaIntrospec.INDEX_SIMPLENAME],
                                          TAG=static in x[JavaIntrospec.INDEX_CLS_IFC_ENM_MODIFIERS] and "strong" or "b",
                                          OTHER="", JTYPE="",
                                          PKGMEM=x[JavaIntrospec.INDEX_NAME].replace(".","/").replace("$","."),
                                          MEMBER="",KEY=x[JavaIntrospec.INDEX_KEY])]
        qc = CodeCompletionPopup(te,sorted(l,key=lambda x:x.lower()),prefix=prefix)
        return qc
    
    def codecompleter_classcontents(self,cls,te,prefix=""):
        '''
        List Importable Class Members
        '''
        l = []
        for x in self[cls][JavaIntrospec.INDEX_CLS_IFC_ENM_CLASSES]+self[cls][JavaIntrospec.INDEX_CLS_IFC_ENM_INTERFACES]+self[cls][JavaIntrospec.INDEX_CLS_IFC_ENM_ENUMS]:
            if prefix and not x[JavaIntrospec.INDEX_SIMPLENAME].lower().startswith(prefix.lower()): continue
            if x[JavaIntrospec.INDEX_CLS_IFC_ENM_VISIBILITY]=='public':
                l += [Java.introspec.skeleton.format(NAME=x[JavaIntrospec.INDEX_SIMPLENAME],TAG="strong",OTHER="",JTYPE="",
                                           PKGMEM=x[JavaIntrospec.INDEX_NAME].replace(".","/").replace("$","."),
                                           MEMBER="",KEY=x[JavaIntrospec.INDEX_KEY])]
        qc = CodeCompletionPopup(te,sorted(l,key=lambda x:x.lower()),prefix=prefix)
        return qc
    
    def codecompleter_constructor(self,clsty,cls,te,vis='public'):
        '''
        List Class Constructors
        '''
        cls = self[cls]
        l = []
        if clsty in ["Class",JavaIntrospec.KEY_CLASS,"Enum",JavaIntrospec.KEY_ENUM]:
            for x in set(tuple(x for x in cls[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS])):
                if x[JavaIntrospec.INDEX_CON_MTH_FLD_VISIBILITY]==vis:
                    if x[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_CONSTRUCTOR:
                        l += [Java.introspec.skeleton.format(NAME=cls[JavaIntrospec.INDEX_SIMPLENAME], 
                                                   TAG="b",
                                                   OTHER="(%s)"%(", ".join(self.__fix(x[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS]))),
                                                   JTYPE="",
                                                   PKGMEM=x[JavaIntrospec.INDEX_CON_MTH_FLD_PARENT].replace(".","/").replace("$","."),
                                                   MEMBER="%s(%s)"%(cls[JavaIntrospec.INDEX_SIMPLENAME],", ".join(x[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS])),
                                                   KEY=x[JavaIntrospec.INDEX_KEY])]
        qc = CodeCompletionPopup(te,sorted(l,key=lambda x:x.lower()))
        return qc
    
    def codecompleter_packages(self,pkg,te,prefix=""):
        '''
        List Package Members
        '''
        l = []
        if not prefix:
            Java.introspec.skeleton.format(NAME="*",TAG="strong",OTHER="",JTYPE="",PKGMEM="",MEMBER="",KEY="PKG")
        for x in self[pkg][JavaIntrospec.INDEX_PKG_SUBPACKAGES]:
            if prefix and not x[JavaIntrospec.INDEX_SIMPLENAME].lower().startswith(prefix.lower()): continue
            if x[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_PACKGAGE:
                l += [Java.introspec.skeleton.format(NAME=x[JavaIntrospec.INDEX_SIMPLENAME],TAG="strong",OTHER="",JTYPE="",PKGMEM="",MEMBER="",KEY=x[JavaIntrospec.INDEX_KEY])]
        for x in self[pkg][JavaIntrospec.INDEX_PKG_CLASSES]+self[pkg][JavaIntrospec.INDEX_PKG_INTERFACES]+self[pkg][JavaIntrospec.INDEX_PKG_ENUMS]:
            if prefix and not x[JavaIntrospec.INDEX_SIMPLENAME].lower().startswith(prefix.lower()): continue
            if x[JavaIntrospec.INDEX_CLS_IFC_ENM_VISIBILITY]=='public':
                l += [Java.introspec.skeleton.format(NAME=x[JavaIntrospec.INDEX_SIMPLENAME],TAG="strong",OTHER="",JTYPE="",
                                           PKGMEM=x[JavaIntrospec.INDEX_NAME].replace(".","/").replace("$","."),
                                           MEMBER="",
                                           KEY=x[JavaIntrospec.INDEX_KEY])]
        qc = CodeCompletionPopup(te,sorted(l,key=lambda x:x.lower()),prefix=prefix)
        return qc
    
    def codecompleter_primitive(self,te,prefix="c"):
        '''
        List 'class' for Primitives
        '''
        return CodeCompletionPopup(te,'class'.startswith(prefix.lower()) and [Java.introspec.skeleton.format(NAME="class",TAG="strong",OTHER="",JTYPE="Class",PKGMEM="",MEMBER="",KEY="FLD")] or [])
    
    def codecompleter_rootpackages(self,te,prefix=""):
        '''
        List Root Packages
        '''
        l = []
        db = self.project.java.values()
        for v in Java.introspec.db.values():
            if not len([x for x in db if x[JavaIntrospec.INDEX_NAME]==v[JavaIntrospec.INDEX_NAME]]): db += [v]
        for x in db:
            if x[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_PACKGAGE and x[JavaIntrospec.INDEX_NAME]!="<default>":
                if prefix and not x[JavaIntrospec.INDEX_SIMPLENAME].lower().startswith(prefix.lower()): continue
                l += [Java.introspec.skeleton.format(NAME=x[JavaIntrospec.INDEX_SIMPLENAME],TAG="strong",OTHER="",JTYPE="",PKGMEM="",MEMBER="",KEY=x[JavaIntrospec.INDEX_KEY])]
        qc = CodeCompletionPopup(te,sorted(l,key=lambda x:x.lower()),prefix=prefix)
        return qc
    
    #===========================================================================
    # Private Helper Methods
    #===========================================================================
    def __addScope(self,scope,tokens,block):
        p=0
        i = len(scope)
        for n,t in enumerate(reversed(tokens[:-1]),start=1):
            if t[1]==")":p-=1
            elif t[1]=="(":p+=1
            elif p==0:
                if t[1] in [";","{","}"]:
                    scope += ["?"] 
                    break
                elif t[0] in ["ClassDeclaration","InterfaceDeclaration","EnumDeclaration"]:
                    scope += [t[1]]
                    break
                elif t[0] in ["MethodDeclaration","ConstructorDeclaration"] \
                        or t[1] in ["if","else","for","do","while","switch","try","catch","finally"]: 
                    st = ""
                    for k in tokens[-n-1:-1]:
                        if k[1]=="throws": break
                        st+=k[1]
                    scope += [st.strip()]
                    break
        if len(scope)==i:
            if block.blockNumber() == 0: scope+=["?"]; return
            pb = block.previous()
            return self.__addScope(scope, pb.userData().tokens, pb)
        return
    
    def __findMemberImport(self,mem,keepVarArgs=False):
        s = ""
        while mem.endswith("..."):
            s=keepVarArgs and "..."+s or "[]"+s
            mem = mem[:-3]
        while mem.endswith("[]"):
            s="[]"+s
            mem = mem[:-2]
        p=0
        while mem:
            if mem[-1]=="<": p+=1
            elif mem[-1]==">": p-=1
            elif p==0: break
            s = mem[-1]+s
            mem=mem[:-1]
        if mem in self or mem in Java.primitive: return mem+s
        if mem in self.allClassTypesInPackage('java.lang'): return "java.lang.%s%s"%(mem,s)
        if mem in self.allClassTypesInPackage(self.package_name()): return "%s.%s%s"%(self.package_name(),mem,s)
        pmem = ""
        for token in [self.document().findBlockByNumber(i).userData().tokens for i in xrange(self.currentBlock().blockNumber())]:
            for k,v,sc in token:
                if k=="Import" and v.endswith(".%s"%mem):
                    pmem = v+s
                    break
                elif k=="Declaration" and ".".join(sc[1:]).endswith(".%s"%mem):
                    pmem = "%s.%s%s"%(self.package_name(),".".join(sc[1:]),s)
            if pmem: break
        return pmem
    
    def __findTokenClass(self,tokmem,tokens):
        try:
            if tokmem[1] in Java.primitive: return tokmem[1]
            if tokmem[0]=="String": return "java.lang.String"
            if tokmem[1]=="class": return "java.lang.Class"
            if tokmem[0] in ["Class","Constructor","Interface","Enum"]: return self.__findMemberImport(tokmem[1])
            if tokmem[1]=="this": return self.__findMemberImport([x for x in tokmem[2] if "(" not in x][-1])
            if tokmem[1]==")":
                p = -1
                tn = None
                for n,token in enumerate(reversed(tokens),start=1):
                    if token[1]=="(": p+=1
                    elif token[1]==")": p-=1
                    if p==0:
                        tn=len(tokens)-n-1
                        break
                if p==0 and tn!=None:
                    if tn<len(tokens) and tn>=0:
                        if tokens[tn][0] in ["Class","Constructor","Interface","Enum"]: return self.__findMemberImport(tokens[tn][1])
                        elif tokens[tn][0]=="Method":
                            if tn==0 or tokens[tn-1][1]!=".":
                                prt = [x for x in tokens[tn][2] if "(" not in x][-1]
                            else:
                                prt = self.__findTokenClass(tokens[tn-2],tokens[:tn-2])
                            if prt:
                                pmem = self.__findMemberImport(prt)
                                if pmem:
                                    if self[pmem][JavaIntrospec.INDEX_KEY] in [JavaIntrospec.KEY_ENUM,JavaIntrospec.KEY_INTERFACE,JavaIntrospec.KEY_CLASS]:
                                        mths = set(self.__findMemberImport(x[JavaIntrospec.INDEX_MTH_RETURNTYPE]) for x in self[pmem][JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS] if x[JavaIntrospec.INDEX_NAME]==tokens[tn][1])
                                    else:
                                        return
                                    if len(mths)==1:
                                        ret = mths.pop()
                                        if ret in Java.primitive: return ret 
                                        return ret
                        elif tokens[tn][0] in ["Symbol","Whitespace"]: return self.__findTokenClass(tokens[0],tokens[1:])
                    else:
                        return self.__findTokenClass(tokens[0],tokens[1:])
            elif tokmem[1]=="(" and tokens:
                return self.__findTokenClass(tokens[0],tokens[1:])
            elif tokmem[0]=="Variable":
                if not(tokens) or tokens[-1][1]!=".":
                    return self.__findVarClass(tokmem[1],tokmem[2])
                prt = self.__findTokenClass(tokens[-2], tokens[:-2])
                if prt:
                    pmem = self.__findMemberImport(prt)
                    if pmem:
                        if self[pmem][JavaIntrospec.INDEX_KEY] in [JavaIntrospec.KEY_ENUM,JavaIntrospec.KEY_INTERFACE,JavaIntrospec.KEY_CLASS]:
                            flds = set(x[JavaIntrospec.INDEX_FLD_TYPE] for x in self[pmem][JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS] if x[JavaIntrospec.INDEX_NAME]==tokmem[1])
                        else:
                            return
                        if len(flds)==1: 
                            ret = flds.pop()
                            if ret in Java.primitive: return ret 
                            return self.__findMemberImport(ret)
            elif tokmem[1]=="]":
                if not(tokens) or tokens[-1][0] not in ["Number","Variable","Symbol"]: return None
                if tokens[-1][0]=="Number" and ("." in tokens[-1][1] or "f" in tokens[-1][1].lower() or "l" in tokens[-1][1].lower()):
                    return None
                elif tokens[-1][0]==["Variable","Symbol"] and self.__findTokenClass(tokens[-1][1],tokens[:-1])!="int": return None
                p = -1
                tn = None
                for n,token in enumerate(reversed(tokens),start=1):
                    if token[1]=="[": p+=1
                    elif token[1]=="]": p-=1
                    if p==0:
                        tn=len(tokens)-n-1
                        break
                if p==0 and tn!=None:
                    ty = self.__findTokenClass(tokens[tn],tokens[:tn])
                    if not(ty) or not(ty.endswith("[]")): return None
                    return ty[:-2]                     
        except:
            pass
        return None
    
    def __findVarClass(self,v,s):
        for vardecl in [self.document().findBlockByNumber(i).userData().vardecl for i in xrange(self.document().blockCount()) if self.document().findBlockByNumber(i).userData()]:
            for ty,vr,sc in vardecl:
                if vr==v and ".".join(s).startswith(".".join(sc)):
                    if ty in Java.primitive: return ty 
                    return self.__findMemberImport(ty)
        return None
    
    def __fix(self,l):
        if isinstance(l,(str,unicode)): return re.sub("(\s|,|^|\<)(java\.lang\.|\<default\>\.|%s)([^\.,\s\>]+)(\.\.\.|\[\]|)(\s|,|$|\>)"%("<" not in self.package_name() and self.package_name() or ""),"\\1\\3\\4\\5",l).replace("<","&lt;").replace(">","&gt;")
        if isinstance(l,(list,tuple)): return tuple(self.__fix(x) for x in l)
        return l 
    
    def __isGeneric(self,g,s):
        for vardecl in [self.document().findBlockByNumber(i).userData().gendecl for i in xrange(self.document().blockCount()) if self.document().findBlockByNumber(i).userData()]:
            for gn,sc in vardecl:
                if gn==g and ".".join(s).startswith(".".join(sc)):
                    return True
        return False
            
    def __isvardecl(self,tokens,block,p=0):
        for t in reversed(tokens):
            if t[1]=="(": p+=1
            elif t[1]==")": p-=1
            elif p==0:
                if t[1]=="=": return False
                elif t[1]==",": return True
        if block.blockNumber()==0: return False
        pb = block.previous()
        return self.__isvardecl(pb.userData().tokens, pb, p)        
            
    def __showCodeCompletionPopup(self,qc):
        if qc.count():
            self.qc = qc
            self.frame.setCodeCompletionPopup(qc)
            self.qc.triggered.connect(lambda a:self.insert(a,self.qc))
            self.qc.highlighted.connect(lambda a:self.javadoc(a,self.qc))
            cursor = self.sv.cursorRect()
            cursor.translate(self.sv.ln.width(),self.sv.toolbar.height())
            self.qc.complete(cursor.bottomLeft())     
            
    def __get_outline(self):
        pkg = self.package_name()
        pt = []
        prt = None
        tokens = []
        for block_tokens in [self.document().findBlockByNumber(i).userData().tokens for i in xrange(self.document().blockCount()) if self.document().findBlockByNumber(i).userData()]:
            tokens += block_tokens
        for i,(ty,nm,sc) in enumerate(tokens):
            if "Declaration" in ty and "?" not in sc:
                if ty=="ClassDeclaration":
                    x = i-1 
                    while x>=0 and tokens[x][1] not in [";","{","}"]: x-=1
                    x=max(x,0)
                    kws = tuple(n[1] for n in tokens[x:i-1] if n[1].strip() and n[1] not in [";","{","}"] and n[1]!="class")
                    oi = i+1
                    while x<len(tokens) and tokens[oi][1] not in ["{",";","}"]: oi+=1
                    ois = tuple(n[1] for n in tokens[i+1:oi] if n[1].strip() and n[1] not in [";","{","}"])
                    gn = ()
                    if "<" in nm:
                        nm,gn = re.sub("^(.*)\<(.*)\>$","\\1;\\2",nm).split(";")
                        gn=tuple(x for x in gn.split(","))
                    cls = [JavaIntrospec.KEY_CLASS,prt and "%s$%s"%(prt[JavaIntrospec.INDEX_NAME],nm) or "%s.%s"%(pkg,nm),nm,prt and prt[JavaIntrospec.INDEX_NAME] or pkg,kws and kws[0] in Java.visibility and kws[0] or '',
                           tuple(x for x in kws if x in Java.modifiers),[],[],[],[],[],gn,[],[],[]]
                    state = ""
                    if "extends" not in ois: ois+=("extends","java.lang.Object")
                    for o in ois:
                        if o=="extends": state="extends"
                        elif o=="implements": state="implements"
                        elif state=="extends": 
                            cls[JavaIntrospec.INDEX_CLS_EXTENDS]+=[self.__findMemberImport(o)]
                            spr = self[self.__findMemberImport(o)]
                            if spr:
                                cls[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS]+=spr[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS]
                                for c in spr[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]: 
                                    c = list(x for x in c)
                                    c[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS]=c[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS][0]
                                    cls[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]+=[tuple(x for x in c)]
                                for c in spr[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS]:
                                    c = list(x for x in c)
                                    c[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS]=c[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS][0]
                                    c[JavaIntrospec.INDEX_NAME]=nm
                                    cls[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS]+=[tuple(x for x in c)]
                        elif state=="implements" and o!=",": cls[JavaIntrospec.INDEX_CLS_ENM_IMPLEMENTS]+=[self.__findMemberImport(o)]
                    prt = cls
                    pt += [cls]
                elif ty=="InterfaceDeclaration":
                    x = i-1 
                    while x>=0 and tokens[x][1] not in [";","{","}"]: x-=1
                    x=max(x,0)
                    kws = tuple(n[1] for n in tokens[x:i-1] if n[1].strip() and n[1] not in [";","{","}"] and n[1]!="class")
                    oi = i+1
                    while x<len(tokens) and tokens[oi][1] not in ["{",";","}"]: oi+=1
                    ois = tuple(n[1] for n in tokens[i+1:oi] if n[1].strip() and n[1] not in [";","{","}"])
                    gn = ()
                    if "<" in nm:
                        nm,gn = re.sub("^(.*)\<(.*)\>$","\\1;\\2",nm).split(";")
                        gn=tuple(x for x in gn.split(","))
                    ifc = [JavaIntrospec.KEY_INTERFACE,prt and "%s$%s"%(prt[JavaIntrospec.INDEX_NAME],nm) or "%s.%s"%(pkg,nm),nm,prt and prt[JavaIntrospec.INDEX_NAME] or pkg,kws and kws[0] in Java.visibility and kws[0] or '',
                           tuple(x for x in kws if x in Java.modifiers),[],[],[],[],[],gn,[]]
                    state = ""
                    for o in ois:
                        if o=="extends": state="extends"
                        elif o=="implements": state="implements"
                        elif state=="extends": 
                            ifc[JavaIntrospec.INDEX_IFC_EXTENDS]+=[self.__findMemberImport(o)]
                            spr = self[self.__findMemberImport(o)]
                            if spr:
                                ifc[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS]+=spr[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS]
                                for c in spr[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]: 
                                    c = list(x for x in c)
                                    c[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS]=c[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS][0]
                                    ifc[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]+=[tuple(x for x in c)]
                    prt = ifc
                    pt += [ifc]
                elif ty=="EnumDeclaration":
                    x = i-1 
                    while x>=0 and tokens[x][1] not in [";","{","}"]: x-=1
                    x=max(x,0)
                    kws = tuple(n[1] for n in tokens[x:i-1] if n[1].strip() and n[1] not in [";","{","}"] and n[1]!="class")
                    oi = i+1
                    while x<len(tokens) and tokens[oi][1] not in ["{",";","}"]: oi+=1
                    ois = tuple(n[1] for n in tokens[i+1:oi] if n[1].strip() and n[1] not in [";","{","}"])
                    gn = ()
                    if "<" in nm:
                        nm,gn = re.sub("^(.*)\<(.*)\>$","\\1;\\2",nm).split(";")
                        gn=tuple(x for x in gn.split(","))
                    enm = [JavaIntrospec.KEY_ENUM,prt and "%s$%s"%(prt[JavaIntrospec.INDEX_NAME],nm) or "%s.%s"%(pkg,nm),nm,prt and prt[JavaIntrospec.INDEX_NAME] or pkg,kws and kws[0] in Java.visibility and kws[0] or '',
                           tuple(x for x in kws if x in Java.modifiers),[],[],[],[],[],gn,[],[]]
                    state = ""
                    for o in ois:
                        if o=="implements": state="implements"
                        elif state=="implements" and o!=",": cls[JavaIntrospec.INDEX_CLS_ENM_IMPLEMENTS]+=[self.__findMemberImport(o)]
                    prt = enm
                    pt += [enm]
                elif ty=="MethodDeclaration":
                    x = i-3
                    while x>=0 and tokens[x][1] not in [";","{","}"]: x-=1
                    x=max(x,0)
                    kws = tuple(n[1] for n in tokens[x:i-1] if n[1].strip() and n[1] not in [";","{","}"])
                    oi = i+1
                    while x<len(tokens) and tokens[oi][1] not in ["{",";","}"]: oi+=1
                    ois = tuple(n[1] for n in tokens[i+1:oi] if n[1].strip() and n[1] not in [";","{","}"])
                    mth = [JavaIntrospec.KEY_METHOD,nm,prt[JavaIntrospec.INDEX_NAME],kws and kws[0] in Java.visibility and kws[0] or '',
                           tuple(x for x in kws if x in Java.modifiers),[],[],kws[-1]]
                    state = ""
                    for o in ois:
                        if o=="(": state="args"
                        elif o==")": state=""
                        elif o=="throws": state="throws"
                        elif state=="args":
                            mth[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS]+=[self.__findMemberImport(o)]
                            state="args-wait"
                        elif state=="args-wait" and o==",": state="args"
                        elif state=="throws" and o!=",": mth[JavaIntrospec.INDEX_THROWS]+=[self.__findMemberImport(o)]
                    for i,x in enumerate(mth):
                        if isinstance(x,list): mth[i]=tuple(n for n in x)
                    prt[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS] += [tuple(x for x in mth)]
                elif ty=="ConstructorDeclaration":
                    x = i-1
                    while x>=0 and tokens[x][1] not in [";","{","}"]: x-=1
                    x=max(x,0)
                    kws = tuple(n[1] for n in tokens[x:i-1] if n[1].strip() and n[1] not in [";","{","}"])
                    oi = i+1
                    while x<len(tokens) and tokens[oi][1] not in ["{",";","}"]: oi+=1
                    ois = tuple(n[1] for n in tokens[i+1:oi] if n[1].strip() and n[1] not in [";","{","}"])
                    cnr = [JavaIntrospec.KEY_CONSTRUCTOR,nm,prt[JavaIntrospec.INDEX_NAME],kws and kws[0] in Java.visibility and kws[0] or '',
                           tuple(x for x in kws if x in Java.modifiers),[],[]]
                    state = ""
                    for o in ois:
                        if o=="(": state="args"
                        elif o==")": state=""
                        elif o=="throws": state="throws"
                        elif state=="args":
                            cnr[JavaIntrospec.INDEX_CON_MTH_ARGUMENTS]+=[self.__findMemberImport(o)]
                            state="args-wait"
                        elif state=="args-wait" and o==",": state="args"
                        elif state=="throws" and o!=",": cnr[JavaIntrospec.INDEX_THROWS]+=[self.__findMemberImport(o)]
                    for i,x in enumerate(cnr):
                        if isinstance(x,list): cnr[i]=tuple(n for n in x)
                    prt[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS] += [tuple(x for x in cnr)]
                elif ty=="VariableDeclaration" and "(" not in sc[-1] and sc[-1] not in Java.reserved:
                    if prt[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_ENUM:
                        kws=('public','static','final')
                    else:
                        x = i-1
                        while x>=0 and tokens[x][1] not in [";","{","}"]: x-=1
                        x=max(x,0)
                        kws = tuple(n[1] for n in tokens[x:i-1] if n[1].strip() and n[1] not in [";","{","}"])
                    prt[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS] += [(JavaIntrospec.KEY_FIELD,nm,prt[JavaIntrospec.INDEX_NAME],kws and kws[0] in Java.visibility and kws[0] or '',
                           tuple(x for x in kws if x in Java.modifiers),self.__findVarClass(nm,sc))]
            elif nm=="}" and "?" not in sc[-1] and "(" not in sc[-1] and sc[-1] not in Java.reserved and pt:
                # Grab Last Item In List
                cls = pt.pop()
                # Convert To Tuple And Add To Parent
                for i,x in enumerate(cls):
                    if isinstance(x,list): cls[i]=tuple(n for n in x)
                cls = tuple(x for x in cls)
                if len(pt)==0:
                    pt = []
                    prt = cls
                else:
                    if cls[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_CLASS:
                        pt[-1][JavaIntrospec.INDEX_CLS_IFC_ENM_CLASSES] += [cls]
                    elif cls[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_INTERFACE:
                        pt[-1][JavaIntrospec.INDEX_CLS_IFC_ENM_INTERFACES] += [cls]
                    elif cls[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_ENUM:
                        pt[-1][JavaIntrospec.INDEX_CLS_IFC_ENM_ENUMS] += [cls]
                    prt = pt[-1]
        return pt and pt[0] or prt

class JavaBlockUserData(QTextBlockUserData):
    def __init__(self,tokens,vardecl,endscope,inVarDeclType,brack,ingeneric,gendecl):
        QTextBlockUserData.__init__(self)
        self.tokens = tokens
        self.vardecl = vardecl
        self.endscope = endscope
        self.inVarDeclType = inVarDeclType
        self.brack = brack
        self.ingeneric = ingeneric
        self.gendecl = gendecl
    def __repr__(self):
        return "JavaBlockUserData(%r,%r,%r,%r,%r,%r)"%(self.tokens,self.vardecl,self.endscope,self.brack,self.ingeneric,self.gendecl) 

class JavaGate(object):
    '''
    This module creates a socket connection to the java library DarkDevJavaGate.
    '''
    VERSION = "0.2.1"
    
    def __init__(self,jdk,darkdev):
        '''
        Initialize JavaGate. It is NOT recommended to create more than one instance of JavaGate.
        
        This function creates a clean build of the DarkDevJavaGate library from source code.
        '''
        exe = lambda e,w: sys.platform.startswith("win") and "%s%s.exe"%(e,w) or e 
        self.javaw = os.path.join(jdk,"bin",exe("java","w"))
        self.javac = os.path.join(jdk,"bin",exe("javac",""))
        pth = os.path.abspath(os.path.join(os.path.dirname(__file__),"java","javagate","com","darkdev","util"))
        # We Only Need To Build DarkDevJavaGate If Running A Development Build
        # - or -
        # There Is No Build Of DarkDevJavaGate
        if darkdev.DEVEL or not os.path.exists(os.path.join(pth,"DarkDevJavaGate.class")):
            # Clean Source Folder
            [os.remove(os.path.join(pth,cls)) for cls in os.listdir(pth) if cls.endswith(".class")]
            # Compile From Source
            darkdev.logger.debug("Building DarkDevJavaGate From Source")
            self.compile_source(os.path.join(pth,"DarkDevJavaGate.java"),os.path.abspath(os.path.join(os.path.dirname(__file__),"java","javagate")))
        # Run DarkDevJavaGate
        p=subprocess.Popen((self.javaw,"-classpath",os.path.abspath(os.path.join(os.path.dirname(__file__),"java","javagate")),"com.darkdev.util.DarkDevJavaGate"))
        darkdev.logger.debug("JavaGate PID: %s"%p.pid)
        time.sleep(2) # Give Time To Open Socket
        # The splash property will determines if output is written to the Splash Screen 
        self.splash = None
    def __connect(self):
        # Connect
        self.sock = socket.socket()
        self.sock.connect(("127.0.0.1",7373))
    def __close(self):
        self.write("close")
        self.sock.close()
    def deinit(self):
        # Send shutdown
        self.write("shutdown")
    def exists(self,*ls):
        self.write("exists",",".join(ls))
        return self.__read()
    def exists_ext(self,cp,*ls):
        self.write("exists_ext",",".join(ls),cp)
        return self.__read()
    def info(self,*ls):
        '''
        Retrieve information about the classes, interfaces, and/or enums
        
        @param *ls: classes, interface, and/or enums to retrieve information about
                    each class/interface/enum should be passed as its own parameter
                    ex: info("java.lang.String","javax.swing.JFrame","java.awt.Color") 
        '''
        self.write("info",",".join(ls))
        return self.__read()
    def info_ext(self,cp,*ls):
        '''
        Retrieve information about the classes, interfaces, and/or enums
        This function allows you to set the URL to use for a URLClassLoader
        
        @param cp: URL to use to create a URLClassLoader
        @param *ls: classes, interface, and/or enums to retrieve information about
                    each class/interface/enum should be passed as its own parameter
                    ex: info("java.lang.String","javax.swing.JFrame","java.awt.Color") 
        '''
        self.write("info_ext",",".join(ls),cp)
        return self.__read()
    def __read(self):
        js=[]
        while True:
            j = ""
            while True:
                s = self.sock.recv(1)
                if s in ["\n","\0"]: break
                j+=s
            if s=="\0": break
            js+=[eval(j.strip())]
            if self.splash and len(js)%100==0: self.splash.setSecondLine("Learning Java - %s Classes Found"%len(js))
        self.__close()
        return js
    def write(self,cmd,*params):
        self.__connect()
        self.sock.sendall("%s\n%s\n"%(cmd,"\n".join(params)))
    def compile_source(self,fname,sourcepath,classpath=""):
        if sourcepath[-1]!=os.sep: sourcepath+=os.sep
        if classpath:
            subprocess.call((self.javac,"-sourcepath",sourcepath,"-classpath",classpath,fname))
        else:
            subprocess.call((self.javac,"-sourcepath",sourcepath,fname))
    def jreinfo(self):
        self.write("jreinfo")
        return self.__read()
    def set_splash(self,s):
        self.splash = s

class JavaIntrospec(object):
    '''
    JavaIntrospec should only be instantiated ONCE per JDK
    Multiple instances will not create any conflicts, however
    a huge amount (unnecessary) of system resources will be used.
    
    This class lists all java packages, classes, interfaces, and 
    enums in the JDK's rt.jar file and ext folder. Other folders 
    and jar/zip files may be searched be calling the search function
    '''
    KEY_PACKGAGE = 'PKG'
    KEY_CLASS = 'CLS'
    KEY_INTERFACE = 'IFC'
    KEY_ENUM = 'ENM'
    KEY_METHOD = 'MTH'
    KEY_CONSTRUCTOR = 'CNR'
    KEY_FIELD = 'FLD'
    
    INDEX_KEY = 0
    INDEX_NAME = 1
    INDEX_SIMPLENAME = 2
    INDEX_CON_MTH_FLD_PARENT = 2
    INDEX_PKG_CLASSES = 3
    INDEX_CON_MTH_FLD_VISIBILITY = 3
    INDEX_CLS_IFC_ENM_PARENT = 3
    INDEX_CON_MTH_FLD_MODIFIERS = 4
    INDEX_PKG_INTERFACES = 4 
    INDEX_CLS_IFC_ENM_VISIBILITY = 4
    INDEX_CON_MTH_ARGUMENTS = 5
    INDEX_FLD_TYPE = 5
    INDEX_PKG_ENUMS = 5
    INDEX_CLS_IFC_ENM_MODIFIERS = 5
    INDEX_THROWS = 6
    INDEX_PKG_SUBPACKAGES = 6
    INDEX_CLS_IFC_ENM_CLASSES = 6
    INDEX_MTH_RETURNTYPE = 7
    INDEX_CLS_IFC_ENM_INTERFACES = 7
    INDEX_CLS_IFC_ENM_ENUMS = 8
    INDEX_CLS_IFC_ENM_METHODS = 9
    INDEX_CLS_IFC_ENM_FIELDS = 10
    INDEX_CLS_IFC_ENM_GENERIC_VARIABLES = 11
    INDEX_CLS_ENM_CONSTRUCTORS = 12
    INDEX_IFC_EXTENDS = 12
    INDEX_CLS_ENM_IMPLEMENTS = 13
    INDEX_CLS_EXTENDS = 14
    def __init__(self,javagate,jdkpath,darkdev):
        '''
        JavaIntrospec Object - See JavaIntrospec.__doc__ for more info
        @param javagate: JavaGate instance
        @param jdkpath: Path to the JDK
        @param darkdev: DarkDev Instance 
        '''
        # We only care about the jre/lib folder
        self.path = os.path.join(jdkpath,"jre","lib")
        self.javagate = javagate
        self.db = {}
        
        # Check For Cache
        if os.path.exists("java.cache"):
            # Load Cache
            y=0
            with open("java.cache","r") as f:
                # Verify Version - In Case Of Updates To Structure
                if f.readline().strip()=="VERSION %s"%JavaGate.VERSION:
                    darkdev.splash.setSecondLine("Learning Java")
                    while True:
                        x = f.readline()
                        if x:
                            y+=1
                            c = eval(x)
                            if y%100==0: darkdev.splash.setSecondLine("Learning Java - %s Classes Found"%y)
                            pkg = self.genpkg(c[JavaIntrospec.INDEX_CLS_IFC_ENM_PARENT],self.db)
                            if c[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_CLASS: pkg[JavaIntrospec.INDEX_PKG_CLASSES]+=[c]
                            elif c[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_INTERFACE: pkg[JavaIntrospec.INDEX_PKG_INTERFACES]+=[c]
                            elif c[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_ENUM: pkg[JavaIntrospec.INDEX_PKG_ENUMS]+=[c]
                            del c, pkg
                        else:
                            break
                        del x
            if y==0:
                # Since Cache Was Empty Or Old Do Initial Scan
                self.rescan(darkdev)
        else:
            # Since No Cache Found Do Initial Scan
            self.rescan(darkdev)
            
        # Create CodeCompletionPopup Skeleton
        self.skeleton = '<html><head><style>i {{color: #aaaaaa}} strong {{font-style: italic}}</style><title>{NAME}{OTHER}</title><meta http-equiv="javadoc" content="http://docs.oracle.com/javase/6/docs/api/{PKGMEM}.html#{MEMBER}"/></head><body><table><tr><td><img src="parsers/java/{KEY}.png" /></td><td><{TAG}>{NAME}</{TAG}>{OTHER}</td><td><i>{JTYPE}</i></td></tr></table></body></html>'
    def genpkg(self,pkg,db):
        '''
        Generate Package Object
        '''
        slots = pkg.split(".")
        p = db
        n = ""
        for s in slots:
            n+="%s%s"%(n and "." or "",s)
            if p==db:
                g = s in p and [p[s]]
            else:
                g = [x for x in p[JavaIntrospec.INDEX_PKG_SUBPACKAGES] if x[JavaIntrospec.INDEX_NAME]==n]
            if g: 
                p=g[0]
            else:
                k=['PKG',n,s,[],[],[],[]]
                if p!=db: p[JavaIntrospec.INDEX_PKG_SUBPACKAGES]+=[k]
                else: db[n]=k
                p=k
        return p
    def __getitem__(self,k):
        if k.endswith("[]") or k.endswith("..."):
            obj = [x for x in self["java.lang.Object"]]
            obj[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS] = ()
            obj[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS] = [x for x in obj[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]]
            obj[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS] += [(JavaIntrospec.KEY_METHOD,'clone','','public',(),((),()),(),k)]
            obj[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS] = tuple(x for x in obj[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS])
            obj[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS] = [x for x in obj[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS]]
            obj[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS] += [(JavaIntrospec.KEY_FIELD,'length','','public',(),'int')]
            obj[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS] = tuple(x for x in obj[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS])
            obj = tuple(x for x in obj)
            return obj
        p=0
        gen = [""]
        while k:
            if k[-1]=="<": p+=1
            elif k[-1]==">": p-=1
            if p==0:
                if k[-1]=="<": k=k[:-1]
                break
            elif k[-1]=="," and p==-1: gen+=[""]
            elif k[-1]!=">" or p!=-1: gen[-1]=k[-1]+gen[-1]
            k=k[:-1]
        slots = k.replace("$",".").split(".")
        p = self.db
        for s in slots:
            if p==self.db:
                g = s in p and [p[s]]
            elif p[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_PACKGAGE:
                for i in [JavaIntrospec.INDEX_PKG_SUBPACKAGES,JavaIntrospec.INDEX_PKG_CLASSES,JavaIntrospec.INDEX_PKG_INTERFACES,JavaIntrospec.INDEX_PKG_ENUMS]:
                    g = [x for x in p[i] if x[JavaIntrospec.INDEX_SIMPLENAME]==s]
                    if g: break
            elif p[JavaIntrospec.INDEX_KEY] in [JavaIntrospec.KEY_CLASS,JavaIntrospec.KEY_INTERFACE,JavaIntrospec.KEY_ENUM]:
                for i in [JavaIntrospec.INDEX_CLS_IFC_ENM_CLASSES,JavaIntrospec.INDEX_CLS_IFC_ENM_INTERFACES,JavaIntrospec.INDEX_CLS_IFC_ENM_ENUMS]:
                    g = [x for x in p[i] if x[JavaIntrospec.INDEX_SIMPLENAME]==s]
                    if g: break
            if g: p=g[0]
            else:p=None; break
        return self.genericize(p, gen)
    def genericize(self,p,gen):
        if p:
            try:
                if p[JavaIntrospec.INDEX_KEY] in [JavaIntrospec.KEY_ENUM,JavaIntrospec.KEY_INTERFACE,JavaIntrospec.KEY_CLASS]:
                    gen = dict(zip(p[JavaIntrospec.INDEX_CLS_IFC_ENM_GENERIC_VARIABLES],[x for x in reversed(gen) if x]))
                    for ltr in p[JavaIntrospec.INDEX_CLS_IFC_ENM_GENERIC_VARIABLES]:
                        if ltr not in gen: gen[ltr]="java.lang.Object"
                    # Copy To List For Write Support
                    p = [x for x in p]
                    # Update Field Generic Types
                    p[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS] = [x for x in p[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS]]
                    flds=[(JavaIntrospec.KEY_FIELD,'class','','public',('static'),'java.lang.Class')]
                    for fld in p[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS]:
                        flds += [(JavaIntrospec.KEY_FIELD,fld[1],fld[2],fld[3],fld[4],self.__generic(fld[5],gen))]
                    p[JavaIntrospec.INDEX_CLS_IFC_ENM_FIELDS] = tuple(x for x in flds)
                    # Update Method Generic Types
                    p[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS] = [x for x in p[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]]
                    mths=[]
                    for mth in p[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS]:
                        mths += [(JavaIntrospec.KEY_METHOD,mth[1],mth[2],mth[3],mth[4],(mth[5],self.__generic(mth[5],gen)),mth[6],self.__generic(mth[7],gen))]
                    p[JavaIntrospec.INDEX_CLS_IFC_ENM_METHODS] = tuple(x for x in mths)
                    if p[JavaIntrospec.INDEX_KEY] != JavaIntrospec.KEY_INTERFACE:
                        # Update Constructor Generic Types
                        p[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS] = [x for x in p[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS]]
                        cnrs=[]
                        for cnr in p[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS]:
                            cnrs += [(JavaIntrospec.KEY_CONSTRUCTOR,cnr[1],cnr[2],cnr[3],cnr[4],(cnr[5],self.__generic(cnr[5],gen)),cnr[6])]
                        p[JavaIntrospec.INDEX_CLS_ENM_CONSTRUCTORS] = tuple(x for x in cnrs)
                    # Copy To Tuple For Read-Only/Hashable Support
                    p = tuple(x for x in p)
            except:
                pass
        return p
    def __contains__(self,k):
        p=0
        while k:
            if k[-1]=="<": p+=1
            elif k[-1]==">": p-=1
            elif p==0: break
            k=k[:-1]
        return k.endswith("[]") or k.endswith("...") or bool(self[k])
    def rescan(self,darkdev=None):
        '''
        Clear cache and rescan the jdk's jre/lib/rt.jar and jre/lib/ext folder 
        '''
        self.db = {}
        self.search(self.path,darkdev=darkdev)
    def search(self,path=None,db=None,darkdev=None):
        '''
        Scan for java packages and classes
        @keyword path: Path to search (default: JDK's jre/lib folder)
        @keyword db: dict to store the packages in (default: instance db property)
        @keyword darkdev: DarkDev Instance - Only Use This When Information Is To Be Sent To The SplashScreen 
        '''
        if not db: db=self.db
        lst = self.__search(path,db,path,darkdev)
        if darkdev: self.javagate.set_splash(darkdev.splash)
        clst = self.javagate.info(*lst)
        self.javagate.set_splash(None)
        del lst
        darkdev.logger.debug("Writing java.cache")
        # Write Cache
        with open("java.cache","w") as f:
            f.write("VERSION %s\n"%JavaGate.VERSION)
            for c in clst:
                f.write(repr(c)+"\n") # Write Each Class/Interface/Enum on it's own line
                f.flush()
        # Sort Based On Packages
        self.__sort(clst, db)
        del clst
    def __sort(self,clst,db):
        for c in clst:
            if not c: continue
            pkg = self.genpkg(c[JavaIntrospec.INDEX_CLS_IFC_ENM_PARENT],self.db)
            if c[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_CLASS: pkg[JavaIntrospec.INDEX_PKG_CLASSES]+=[c]
            elif c[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_INTERFACE: pkg[JavaIntrospec.INDEX_PKG_INTERFACES]+=[c]
            elif c[JavaIntrospec.INDEX_KEY]==JavaIntrospec.KEY_ENUM: pkg[JavaIntrospec.INDEX_PKG_ENUMS]+=[c]
            del pkg
        del c
    def __search(self,path,db,start,darkdev):
        lst = []
        if os.path.isfile(path):
            # We only need to scan the rt.jar file and ext subfolder (if searching the JDK's jre/lib folder)
            if start!=self.path or (os.path.basename(path)=="rt.jar" or os.path.dirname(path)=="ext"):
                if darkdev: darkdev.splash.setSecondLine("Java - Searching %s"%path[len(start)+1:])
                # Check to see if file is a class file, a jar/zip file, or file that we do not need
                if path.lower().endswith(".class") and "$" not in os.path.basename(path):
                    lst += ["%s.%s"%(os.path.dirname(path).replace(start+os.sep,"").replace(os.sep,"."),os.path.basename(path)[:-6])]
                elif path.lower()[-4:] in [".jar",".zip"]:
                    zf = ZipFile(path,"r")
                    for f in [x for x in sorted(zf.namelist()) if x.lower().endswith(".class") and "$" not in x]:
                        lst += ["%s.%s"%(os.path.dirname(f).replace("/","."),os.path.basename(f)[:-6])]
                    zf.close()
        elif os.path.isdir(path):
            for f in [os.path.join(path,f) for f in os.listdir(path)]:
                lst+=self.__search(f,db,start,darkdev)
        return lst
    def __generic(self,lst,key):
        if isinstance(lst,(str,unicode)):
            for k,v in key.iteritems():
                lst = re.sub("(^|[^A-Za-z0-9_])%s([^A-Za-z0-9_]|$)"%k,"\\1%s\\2"%v,lst.replace("$","."))
            return lst
        if isinstance(lst,(list,tuple)): return tuple(self.__generic(x,key) for x in lst)
        return lst